﻿using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Animation;
using Red.Core.Extensions;

namespace Red.Launcher
{
    public class LogoSpecs
    {
        public double Width { get; set; }
        public double X { get; set; }
        public double Y { get; set; }
        public Thickness ParentMargin { get; set; }
    }

    public partial class LauncherLogo : Window
    {
        #region Private Data

        // True if the logo is being directly moved by the user
        private bool _dragging;
        private bool _parentMouseDown;
        private bool _animating;

        #endregion Private Data

        #region Dependency Properties

        public static readonly DependencyProperty OffsetXProperty = DependencyProperty.Register( "OffsetX", typeof( double ), typeof( LauncherLogo ), new PropertyMetadata( Offset_Changed ) );
        public static readonly DependencyProperty OffsetYProperty = DependencyProperty.Register( "OffsetY", typeof( double ), typeof( LauncherLogo ), new PropertyMetadata( Offset_Changed ) );
        public static readonly DependencyProperty LogoWidthProperty = DependencyProperty.Register( "LogoWidth", typeof( double ), typeof( LauncherLogo ) );
        public static readonly DependencyProperty IsLargeProperty = DependencyProperty.Register( "IsLarge", typeof( bool ), typeof( LauncherLogo ), new PropertyMetadata( true, IsLarge_Changed ) );

        #endregion Dependency Properties

        #region Properties

        public double LogoWidth
        {
            get { return (double)GetValue( LogoWidthProperty ); }
            set { SetValue( LogoWidthProperty, value ); }
        }

        public double OffsetX
        {
            get { return (double)GetValue( OffsetXProperty ); }
            set { SetValue( OffsetXProperty, value ); }
        }

        public double OffsetY
        {
            get { return (double)GetValue( OffsetYProperty ); }
            set { SetValue( OffsetYProperty, value ); }
        }

        public bool IsLarge
        {
            get { return (bool)GetValue( IsLargeProperty ); }
            set { SetValue( IsLargeProperty, value ); }
        }

        public LogoSpecs Large { get; private set; }
        public LogoSpecs Small { get; private set; }

        #endregion Properties

        #region C-Tor

        public LauncherLogo()
        {
            InitializeComponent();

            Loaded += LauncherLogo_Loaded;

            _setLargeLogo.Click += _setLargeLogo_Click;
            _setSmallLogo.Click += _setSmallLogo_Click;
        }

        #endregion C-Tor

        #region WndProc

        public struct WINDOWPOS
        {
            public IntPtr hwnd;
            public IntPtr hwndInsertAfter;
            public int x;
            public int y;
            public int cx;
            public int cy;
            public UInt32 flags;
        };

        private IntPtr WndProc( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled )
        {
            switch ( msg )
            {
                case 0x46://WM_WINDOWPOSCHANGING
                    {
                        WINDOWPOS wp = (WINDOWPOS)Marshal.PtrToStructure( lParam, typeof( WINDOWPOS ) );

                        if ( Mouse.LeftButton != MouseButtonState.Pressed && !_parentMouseDown && !_animating )
                        {
                            wp.flags = wp.flags | 2; //SWP_NOMOVE
                            Marshal.StructureToPtr( wp, lParam, false );
                        }

                        if ( wp.y <= 0 && Mouse.LeftButton != MouseButtonState.Pressed )
                        {
                            _parentMouseDown = false;
                        }
                    }

                    break;
            }
            return IntPtr.Zero;
        }

        #endregion WndProc

        #region Event Handlers

        private void Window_MouseDoubleClick( object sender, MouseButtonEventArgs e )
        {
            IsLarge = !IsLarge;
        }

        private void LauncherLogo_Loaded( object sender, RoutedEventArgs e )
        {
            HwndSource source = HwndSource.FromHwnd( new WindowInteropHelper( this ).Handle );
            source.AddHook( new HwndSourceHook( WndProc ) );

            Owner.LocationChanged += ParentWindow_PositionChanged;
            Owner.MouseLeftButtonDown += ParentWindow_MouseLeftButtonDown;

            LocationChanged += LauncherLogo_PositionChanged;
            MouseLeftButtonDown += LauncherLogo_MouseLeftButtonDown;
        }

        private static void Offset_Changed( DependencyObject d, DependencyPropertyChangedEventArgs e )
        {
            ( (LauncherLogo)d ).UpdatePosition();
        }

        private static void IsLarge_Changed( DependencyObject d, DependencyPropertyChangedEventArgs e )
        {
            LauncherLogo logo = (LauncherLogo)d;

            if ( logo.IsLarge )
            {
                logo.Resize( logo.Large.Width, logo.Large.X, logo.Large.Y );
                logo.Growing?.Invoke();
            }
            else
            {
                logo.Resize( logo.Small.Width, logo.Small.X, logo.Small.Y );
                logo.Shrinking?.Invoke();
            }
        }

        private void ParentWindow_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )
        {
            _parentMouseDown = true;
        }

        private void LauncherLogo_MouseLeftButtonDown( object sender, MouseButtonEventArgs e )
        {
            // Events are called internally, but execution blocks inside DragMove() until the mouse is released
            _dragging = true;
            DragMove();
            _dragging = false;
        }

        private void ParentWindow_PositionChanged( object sender, EventArgs e )
        {
            // The parent was moved, only update if it's not in response to the logo being move
            if ( !_dragging )
            {
                UpdatePosition();
            }
        }

        private void LauncherLogo_PositionChanged( object sender, EventArgs e )
        {
            // The logo was moved, make sure to only update the parent if the logo was moved directly by the user
            if ( _dragging )
            {
                UpdateParentPosition();
            }
        }

        private void _setLargeLogo_Click( object sender, RoutedEventArgs e )
        {
            IsLarge = true;
        }

        private void _setSmallLogo_Click( object sender, RoutedEventArgs e )
        {
            IsLarge = false;
        }

        #endregion Event Handlers

        #region Private Methods

        private void UpdateParentPosition()
        {
            Owner.SetPositionRelativeTo( this, OffsetX, OffsetY );
        }

        private void Resize( double targetLogoWidth, double targetOffsetX, double targetOffsetY )
        {
            _animating = true;

            DoubleAnimation logoWidthAnimation = new DoubleAnimation( targetLogoWidth, TimeSpan.FromSeconds( 0.5 ) );
            DoubleAnimation offsetXAnimation = new DoubleAnimation( targetOffsetX, TimeSpan.FromSeconds( 0.5 ) );
            DoubleAnimation offsetYAnimation = new DoubleAnimation( targetOffsetY, TimeSpan.FromSeconds( 0.5 ) );

            logoWidthAnimation.EasingFunction = new BackEase() { EasingMode = EasingMode.EaseInOut };
            offsetXAnimation.EasingFunction = logoWidthAnimation.EasingFunction;
            offsetYAnimation.EasingFunction = logoWidthAnimation.EasingFunction;

            // Fed through the dispatcher to add another frame delay, as otherwise the WndProc function will cause us to ignore the final frame of animation 
            logoWidthAnimation.Completed += ( object completedSender, EventArgs completedArgs ) => { Dispatcher.BeginInvoke( (Action)( () => { _animating = false; } ) ); };

            BeginAnimation( LogoWidthProperty, logoWidthAnimation );
            BeginAnimation( OffsetXProperty, offsetXAnimation );
            BeginAnimation( OffsetYProperty, offsetYAnimation );
        }

        #endregion Private Methods

        #region Public Methods

        public void WriteToSettings( UserSettings settings )
        {
            settings.IsLogoLarge = IsLarge;
        }

        public void LoadFromSettings( UserSettings settings )
        {
            Large = settings.LogoLarge;
            Small = settings.LogoSmall;

            LogoSpecs specs = settings.IsLogoLarge ? Large : Small;

            LogoWidth = specs.Width;
            OffsetX = specs.X;
            OffsetY = specs.Y;

            IsLarge = settings.IsLogoLarge;
        }

        public void UpdatePosition()
        {
            if( Owner != null )
            {
                this.SetPositionRelativeTo( Owner, -OffsetX, -OffsetY );
            }
        }

        #endregion Public Methods

        #region Events

        public delegate void LogoResizeHandler();
        public event LogoResizeHandler Shrinking;
        public event LogoResizeHandler Growing;

        #endregion Events

    }
}
